#pragma once
#include <brpc/channel.h>
#include <mutex>
#include <memory>
#include <string>
#include <vector>
#include <algorithm>
#include <unordered_map>
#include <unordered_set>
#include "logger.hpp"

////////////////////////////////////////////////////////////////
/// @file channel.hpp
/// @brief 基于brpc的服务信道管理组件
/// @details 提供服务信道的创建、管理和负载均衡功能
///          - ServiceChannel: 管理单个服务的多个节点信道
///          - ServiceManager: 管理多个服务的信道管理器
/// @author quan
/// @date 2025-10-17
////////////////////////////////////////////////////////////////

/// @class ServiceChannel
/// @brief 单个服务的信道管理类
/// @details 负责管理同一服务的多个节点信道，提供以下功能：
///          - 节点上线/下线时动态添加/移除信道
///          - 轮询方式选择可用信道，实现简单负载均衡
///          - 线程安全的信道访问
/// @note 
///   - 所有公共方法均线程安全，内部使用互斥锁保护
///   - 信道选择采用轮询(Round-Robin)策略
class ServiceChannel
{
public:
    using ptr = std::shared_ptr<ServiceChannel>;
    using ChannelPtr = std::shared_ptr<brpc::Channel>;

public:
    /// @brief 构造函数，创建服务信道管理器
    /// @param service_name 服务名称，用于日志标识（如："/service/user"）
    /// @note 构造时会初始化brpc::ChannelOptions，设置默认的RPC参数
    ServiceChannel(const std::string &service_name)
        : _service_name(service_name), _cur_index(-1)
    {
        initOptions();
    }

    /// @brief 处理服务节点上线事件
    /// @param host 节点地址，格式为"ip:port"（如："127.0.0.1:8000"）
    /// @details 
    ///   1. 检查节点是否已存在，避免重复上线
    ///   2. 创建并初始化brpc::Channel
    ///   3. 将信道添加到可用列表和映射表
    /// @note 
    ///   - 如果节点已存在会打印警告日志但不会报错
    ///   - 如果信道初始化失败会打印错误日志并放弃该节点
    ///   - 线程安全
    void online(const std::string &host)
    {
        std::lock_guard<std::mutex> lock(_mutex);
        if (_hosts.count(host))
        {
            swarn("服务重复上线 {}-{}", _service_name, host);
            return;
        }
        auto channel = std::make_shared<brpc::Channel>();
        if (-1 == channel->Init(host.c_str(), &_options))
        {
            serror("信道初始化失败 {}-{}", _service_name, host);
            return;
        }
        _hosts[host] = channel;
        _channels.push_back(channel);

        sinfo("服务上线, {}-{}", _service_name, host);
    }

    /// @brief 处理服务节点下线事件
    /// @param host 节点地址，格式为"ip:port"（如："127.0.0.1:8000"）
    /// @details 
    ///   1. 检查节点是否存在
    ///   2. 从信道列表中移除对应信道
    ///   3. 从映射表中删除节点记录
    /// @note 
    ///   - 如果节点不存在会打印警告日志但不会报错
    ///   - 移除后不影响正在进行的RPC调用
    ///   - 线程安全
    void offline(const std::string &host)
    {
        std::lock_guard<std::mutex> lock(_mutex);
        auto it = _hosts.find(host);
        if (it == _hosts.end())
        {
            swarn("无法下线不存在的节点 {}-{}", _service_name, host);
            return;
        }

        _channels.erase(
            std::remove_if(_channels.begin(),
                           _channels.end(),
                           [&](const ChannelPtr &p)
                           {
                               return p == it->second;
                           }),
            _channels.end());
        _hosts.erase(it);

        sinfo("服务下线, {}-{}", _service_name, host);
    }

    /// @brief 获取一个可用信道
    /// @return 信道智能指针，如果无可用信道则返回nullptr
    /// @details 使用轮询(Round-Robin)算法选择下一个可用信道
    /// @note 
    ///   - 线程安全
    ///   - 每次调用会自动切换到下一个信道，实现负载均衡
    ///   - 如果当前无可用节点，会返回nullptr并记录错误日志
    /// @warning 调用方需要检查返回值是否为空
    ChannelPtr get()
    {
        std::lock_guard<std::mutex> lock(_mutex);
        if (_channels.empty())
        {
            swarn("{} 暂无可用信道", _service_name);
            return nullptr;
        }
        _cur_index = (_cur_index + 1) % _channels.size();
        return _channels[_cur_index];
    }

private:
    /// @brief 初始化brpc通道选项
    /// @details 设置RPC通信的默认参数：
    ///   - backup_request_ms: -1 (不使用backup request)
    ///   - timeout_ms: -1 (不设置超时)
    ///   - max_retry: 3 (最多重试3次)
    ///   - protocol: "baidu_std" (使用百度标准协议)
    /// @note 后续可改为从配置文件读取，提高灵活性
    void initOptions()
    {
        _options.backup_request_ms = -1;
        _options.timeout_ms = -1;
        _options.max_retry = 3;
        _options.protocol = "baidu_std";
    }

private:
    /// @brief 互斥锁
    std::mutex _mutex;
    /// @brief 服务名
    std::string _service_name;
    /// @brief 当前轮转下标
    size_t _cur_index;
    /// @brief 可用信道列表
    std::vector<ChannelPtr> _channels;
    /// @brief brpc通道初始化选项
    brpc::ChannelOptions _options;
    /// @brief 主机地址与信道映射关系
    std::unordered_map<std::string, ChannelPtr> _hosts;
};

/// @class ServiceManager
/// @brief 多服务信道管理器
/// @details 负责管理多个服务的ServiceChannel实例，提供统一的访问接口
///          - 支持预订阅指定的服务列表
///          - 提供服务上线/下线的回调接口，可对接服务发现组件
///          - 根据服务名获取对应的RPC信道
/// @note 
///   - 线程安全，内部的ServiceChannel保证了并发安全
///   - 订阅的服务列表在构造时确定，之后不可修改
class ServiceManager
{
public:
    using ptr = std::shared_ptr<ServiceManager>;
    using ChannelPtr = std::shared_ptr<brpc::Channel>;

public:
    /// @brief 构造函数，初始化服务管理器
    /// @param subscribe_services 需要订阅的服务名称集合
    /// @details 为每个订阅的服务创建对应的ServiceChannel实例
    /// @note 
    ///   - 订阅列表一经设置不可修改，确保服务依赖关系明确
    ///   - 建议在应用启动时一次性配置所有依赖的服务
    ServiceManager(const std::unordered_set<std::string> &subscribe_services)
        : _subscribe_services(subscribe_services)
    {
        for (const auto &service_name : _subscribe_services)
            _services[service_name] = std::make_shared<ServiceChannel>(service_name);
    }

    /// @brief 获取指定服务的一个可用信道
    /// @param service_name 服务名称
    /// @return 信道智能指针，失败时返回nullptr
    /// @details 从对应服务的ServiceChannel中轮询获取一个可用信道
    /// @note 
    ///   - 如果服务未订阅，会记录错误日志并返回nullptr
    ///   - 如果服务暂无可用节点，会返回nullptr
    ///   - 调用方必须检查返回值是否为空
    /// @warning 未订阅的服务会返回nullptr，需提前在构造函数中订阅
    ChannelPtr getChannel(const std::string &service_name)
    {
        if (_subscribe_services.find(service_name) != _subscribe_services.end())
            return _services[service_name]->get();
        serror("未定阅 {}, 请检查代码", service_name);
        return nullptr;
    }

    /// @brief 服务节点上线回调函数
    /// @param instance_name 服务实例完整路径，格式："服务名/节点标识"（如："/service/user/instance1"）
    /// @param host 节点地址，格式："ip:port"（如："127.0.0.1:8000"）
    /// @details 
    ///   1. 从instance_name中提取服务名（'/'之前的部分）
    ///   2. 检查是否为订阅的服务
    ///   3. 如果是订阅服务，通知对应的ServiceChannel处理上线
    /// @note 
    ///   - 通常作为ServiceDiscovery的回调函数使用
    ///   - 未订阅的服务上线会记录信息日志，但不会报错
    ///   - 函数内部会提取服务名，调用方无需手动解析
    void onServiceOnline(const std::string &instance_name, const std::string &host)
    {
        std::string service_name = instance_name.substr(0, instance_name.find_last_of('/'));
        
        if (_subscribe_services.find(service_name) != _subscribe_services.end())
            _services[service_name]->online(host);
        else
            sinfo("未订阅服务上线, {}-{}", service_name, host);
    }

    /// @brief 服务节点下线回调函数
    /// @param instance_name 服务实例完整路径，格式："服务名/节点标识"（如："/service/user/instance1"）
    /// @param host 节点地址，格式："ip:port"（如："127.0.0.1:8000"）
    /// @details 
    ///   1. 从instance_name中提取服务名（'/'之前的部分）
    ///   2. 检查是否为订阅的服务
    ///   3. 如果是订阅服务，通知对应的ServiceChannel处理下线
    /// @note 
    ///   - 通常作为ServiceDiscovery的回调函数使用
    ///   - 未订阅的服务下线会记录信息日志，但不会报错
    ///   - 函数内部会提取服务名，调用方无需手动解析
    void onServiceOffline(const std::string &instance_name, const std::string &host)
    {
        std::string service_name = instance_name.substr(0, instance_name.find_last_of('/'));
        if (_subscribe_services.find(service_name) != _subscribe_services.end())
            _services[service_name]->offline(host);
        else
            sinfo("未定阅服务下线, {}-{}", service_name, host);
    }

private:
    /// @brief 订阅的服务
    const std::unordered_set<std::string> _subscribe_services;
    /// @brief 服务名到信道管理器的映射
    std::unordered_map<std::string, ServiceChannel::ptr> _services;
};